﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace GameEngine.PDNParser
{
    /// <summary>
    /// Class for scanning PDN file and tokenize it to scanner token
    /// </summary>
    public class PDNScanner
    {
        private const char keyLineCommentChar = '%';
        private const char keyUnderscoreChar = '_';
        private const char keyLComment = '{';
        private const char keyRComment = '}';
        private const char keyString = '\"';
        private const char keySetup = '/';
        private const char keyCapture = 'x';
        private const char keyEscapeChar = '\\';


        private ILocalizer _localizer;

        private Dictionary<char, char> _mapEatStringMap;

        private List<ScannerToken> _lstOutput;

        private bool _oEatingAll;
        private char _cStopEatingSign;
        private string _sEatAllString;

        /// <summary>
        /// Initialization of scanner stream
        /// </summary>
        public PDNScanner(ILocalizer localizer)
        {
            _lstOutput = new List<ScannerToken>();
            _localizer = localizer;

            // init  map of signs which do not separate input in tokens but eats all in one string
            _mapEatStringMap = new Dictionary<char, char>();
            _mapEatStringMap[keyLComment] = keyRComment;
            _mapEatStringMap[keyString] = keyString;
            _mapEatStringMap[keySetup] = keySetup;

            _oEatingAll = false;
        }

        public List<ScannerToken> ScannerTokens
        {
            get
            {
                return _lstOutput;
            }
        }

        /// <summary>
        /// Scans the file and returns the list of tokens
        /// </summary>
        /// <param name="sFile"></param>
        /// <returns></returns>
        public List<ScannerToken> ScanFile(Stream s)
        {
            int lLineNumber = 1;

            // open stream
            using (StreamReader stream = new StreamReader(s))
            {
                while (stream.EndOfStream == false)
                {
                    // read the line
                    string sLine = stream.ReadLine();
                    
                    // scan the line
                    ScanLine(lLineNumber, sLine);

                    lLineNumber++;
                }

                //stream.Close();
            }

            // check for eating all Error
            if (_oEatingAll == true)
            {
              //  throw new ApplicationException(
            //            string.Format(
            //                _localizer.GetStringByKey("PDNEndingSignNotFound")
             //               , _cStopEatingSign
              //              ));
            }

            return _lstOutput;
        }

        /// <summary>
        /// Scans one line for tokens
        /// </summary>
        /// <param name="sLine"></param>
        private void ScanLine(int lLineNumber, string sLine)
        {
            LineStream stream = new LineStream(sLine);

            // check, if we are in the eating all regime
            if (_oEatingAll == true)
            {
                DoEatingAll(stream, lLineNumber);
                if (stream.EOF)
                    return;
            }

            while (stream.EOF == false)
            {
                char c = stream.Read();

                // if line comment found, exit
                if (c == keyLineCommentChar)
                    return;

                // ignore whitespace characters
                if (char.IsWhiteSpace(c))
                    continue;

                // check for eating all regime
                char cStopSign;
                if (_mapEatStringMap.TryGetValue(c, out cStopSign) == true)
                {
                    _oEatingAll = true;
                    _cStopEatingSign = cStopSign;
                    _sEatAllString = string.Empty;

                    ScannerToken scanToken = new ScannerToken();
                    scanToken.enumScannerToken = enumScannerToken.Sign;
                    scanToken.cSign = c;
                    scanToken.lLineNumber = lLineNumber;
                    _lstOutput.Add(scanToken);

                    DoEatingAll(stream, lLineNumber);
                    if (stream.EOF == true)
                        return;
                    continue;
                }

                if (IsLetterSign(c))
                {
                    // if it is letter
                    ScannerToken token = new ScannerToken();
                    token.enumScannerToken = enumScannerToken.String;
                    if (c == keyCapture)
                    {
                        token.sString = c.ToString();
                    }
                    else
                    {
                        token.sString = c + ReadString(stream);
                    }
                    
                    token.lLineNumber = lLineNumber;
                    _lstOutput.Add(token);
                }
                else if (char.IsNumber(c))
                {
                    // if it is number
                    ScannerToken token = new ScannerToken();
                    token.enumScannerToken = enumScannerToken.Number;
                    token.sString = c + ReadNumber(stream);
                    token.lLineNumber = lLineNumber;
                    _lstOutput.Add(token);
                }
                else
                {
                    // else it is sign
                    ScannerToken token = new ScannerToken();
                    token.enumScannerToken = enumScannerToken.Sign;
                    token.cSign = c;
                    token.lLineNumber = lLineNumber;
                    _lstOutput.Add(token);
                }
            }
        }

        private void DoEatingAll(LineStream stream, int lLineNumber)
        {
            while (stream.EOF == false)
            {
                char c = stream.Read();
                // if we are at the end
                if (c == _cStopEatingSign)
                {
                    // add string token
                    ScannerToken token = new ScannerToken();
                    token.enumScannerToken = enumScannerToken.String;
                    token.sString = _sEatAllString;
                    token.lLineNumber = lLineNumber;
                    _lstOutput.Add(token);

                    // add ending token
                    token = new ScannerToken();
                    token.enumScannerToken = enumScannerToken.Sign;
                    token.cSign = c;
                    token.lLineNumber = lLineNumber;
                    _lstOutput.Add(token);

                    _oEatingAll = false;
                    return;
                }

                if (c == keyEscapeChar)
                {
                    c = stream.Read();
                }

                _sEatAllString += c;
            }
        }

        /// <summary>
        /// Reads the next string token
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private string ReadString(LineStream stream)
        {
            string sString = string.Empty;
            while (stream.EOF == false)
            {
                char c = stream.Read();

                // following chars of string can be letters of numbers
                if (IsLetterSign(c) || char.IsNumber(c))
                    sString += c;
                else
                {
                    // next character is not letter exit
                    stream.Unread();
                    break;
                }
            }

            return sString;
        }

        
        /// <summary>
        /// Reads the next number token
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        private string ReadNumber(LineStream stream)
        {
            string sNumber = string.Empty;
            while (stream.EOF == false)
            {
                char c = stream.Read();

                if (char.IsNumber(c))
                    sNumber += c;
                else
                {
                    // next character is not number exit
                    stream.Unread();
                    break;
                }
            }

            return sNumber;
        }

        /// <summary>
        /// Checks it if is letter or special letter characters
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private bool IsLetterSign(char c)
        {
            return char.IsLetter(c) == true || c == keyUnderscoreChar;
        }

        /// <summary>
        /// Class making reader stream out of string
        /// </summary>
        private class LineStream
        {
            private string _sLine;
            private int _lIndex;

            /// <summary>
            /// Initialization
            /// </summary>
            /// <param name="sLine"></param>
            public LineStream(string sLine)
            {
                _sLine = sLine;
                _lIndex = 0;
            }

            /// <summary>
            /// Test if line is EOF
            /// </summary>
            public bool EOF
            {
                get
                {
                    return _lIndex >= _sLine.Length;
                }
            }

            /// <summary>
            /// Checks the next char
            /// </summary>
            /// <returns></returns>
            public char Peek()
            {
                // if (this.EOF)
                    //throw new ApplicationException("Line Is EOF.");
                return _sLine[_lIndex];
            }

            /// <summary>
            /// Reads the next character
            /// </summary>
            /// <returns></returns>
            public char Read()
            {
                // if (this.EOF)
                    //throw new ApplicationException("Line Is EOF.");
                return _sLine[_lIndex++];
            }

            /// <summary>
            /// Returns one character back
            /// </summary>
            public void Unread()
            {
                _lIndex--;
            }
        }
    }
}
